Coding Headstart
================

* :download:`Download example <PyObjCExample-Coding Headstart.zip>`

This is a Python version of the sample code in the
CalendarStore Coding Headstart for WWDC'07.

Note that this implementation is incomplete, the bits
that should be implemented by the reader have not
been implemented yet.


.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

AppController.py
................

.. sourcecode:: python

    import objc
    from CalendarStore import CalCalendarStore, CalEvent, CalTask
    from Cocoa import NSApp, NSApplication, NSDate, NSLog, NSObject
    
    
    class AppController(NSObject):
        mainWindow = objc.IBOutlet()
        taskCreationDialog = objc.IBOutlet()
        priorityPopup = objc.IBOutlet()
        eventCreationDialog = objc.IBOutlet()
        calendarData = objc.IBOutlet()
    
        calItemTitle = objc.ivar()
        calItemStartDate = objc.ivar()
        calItemEndDate = objc.ivar()
    
        objc.synthesize("calItemTitle", copy=True)
        objc.synthesize("calItemStartDate", copy=True)
        objc.synthesize("calItemEndDate", copy=True)
    
        @objc.IBAction
        def showTaskCreationDialog_(self, sender):
            # Set default values for the title, start date and priority
            # Cocoa bindings will clear out the related fields in the sheet
            self._.calItemTitle = None
            self._.calItemStartDate = NSDate.date()
            NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
                self.taskCreationDialog,
                self.mainWindow,
                self,
                "didEndSheet:returnCode:contextInfo:",
                None,
            )
    
        @objc.IBAction
        def showEventCreationDialog_(self, sender):
            # Set default values for the title and start/end date
            # Cocoa bindings will clear out the related fields in the sheet
            self._.calItemTitle = None
            self._.calItemStartDate = NSDate.date()
            self._.calItemEndDate = NSDate.dateWithTimeIntervalSinceNow_(3600)
            NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
                self.eventCreationDialog,
                self.mainWindow,
                self,
                "didEndSheet:returnCode:contextInfo:",
                None,
            )
    
        # Called when the "Add" button is pressed on the event/task entry sheet
        # This starts the sheet dismissal process
        @objc.IBAction
        def dismissDialog_(self, sender):
            NSApp.endSheet_(sender.window())
    
        @objc.selectorFor(
            NSApplication.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_
        )
        def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
            # Find out which calendar was selected for the new event/task
            # We do this using the calendarData array controller which is bound to
            # the calendar popups in the sheet
            selectedCalendar = None
            count = len(self.calendarData.selectedObjects())
            if count > 0:
                selectedCalendarUID = self.calendarData.selectedObjects()[0].uid()
                selectedCalendar = CalCalendarStore.defaultCalendarStore().calendarWithUID_(
                    selectedCalendarUID
                )
    
            # Create an event/task based on which sheet was used
            if sheet is self.taskCreationDialog:
                if self._.calItemTitle is None:
                    self._.calItemTitle = "My Task"
    
                self.createNewTaskWithCalendar_title_priority_dueDate_(
                    selectedCalendar,
                    self._.calItemTitle,
                    self.priorityPopup.selectedTag(),
                    self._.calItemStartDate,
                )
    
            else:
                if self._.calItemTitle is None:
                    self._.calItemTitle = "My Event"
    
                self.createNewEventWithCalendar_title_startDate_endDate_(
                    selectedCalendar,
                    self._.calItemTitle,
                    self._.calItemStartDate,
                    self._.calItemEndDate,
                )
    
            # Dismiss the sheet
            sheet.orderOut_(self)
    
        def createNewEventWithCalendar_title_startDate_endDate_(
            self, calendar, title, startDate, endDate
        ):
            # Create a new CalEvent object
            newEvent = CalEvent.event()
    
            # Set the calendar, title, start date and end date on the new event
            # using the parameters passed to this method
            newEvent._.calendar = calendar
            newEvent._.title = title
            newEvent._.startDate = startDate
            newEvent._.endDate = endDate
    
            # Save the new event to the calendar store (CalCalendarStore) and
            # return it
            res, err = CalCalendarStore.defaultCalendarStore().saveEvent_span_error_(
                newEvent, 0, None
            )
            if res:
                return newEvent
    
            NSLog("error:%@", err.localizedDescription())
            return None
    
        def createNewTaskWithCalendar_title_priority_dueDate_(
            self, calendar, title, priority, dueDate
        ):
            # Create a new CalTask object
            newTask = CalTask.task()
    
            # Set the calendar, title, priority and due date on the new task
            # using the parameters passed to this method
            newTask._.calendar = calendar
            newTask._.title = title
            newTask._.priority = priority
            newTask._.dueDate = dueDate
    
            # Save the new task to the calendar store (CalCalendarStore) and
            # return it
            res, err = CalCalendarStore.defaultCalendarStore().saveTask_error_(
                newTask, None
            )
            if res:
                return newTask
    
            NSLog("error:%@", err.localizedDescription())
            return None

.. rst-class:: tabbertab

CalController.py
................

.. sourcecode:: python

    """
    Bindings and notification support for Calendar data used
    by this application.  Exposes read-only collections
    (calendars, events, tasks) as observable entities.
    """
    
    from CalendarStore import (
        CalCalendarsChangedExternallyNotification,
        CalCalendarsChangedNotification,
        CalCalendarStore,
        CalEventsChangedExternallyNotification,
        CalEventsChangedNotification,
        CalPriorityHigh,
        CalPriorityMedium,
        CalTasksChangedExternallyNotification,
        CalTasksChangedNotification,
    )
    from Cocoa import NSDate, NSNotificationCenter, NSObject, NSString, NSValueTransformer
    
    highPriority = "High"
    normPriority = "Normal"
    lowPriority = "Low"
    nonePriority = "None"
    
    
    class CalPriorityToStringTransformer(NSValueTransformer):
        """
        The CalPriorityToStringTransformer class allows easy conversion between
        CalPriority values (0-9) and human-readable priority strings (High,
        Normal, Low, None). This allows us to populate the priority dropdown
        using bindings
        """
    
        @classmethod
        def transformedValueClass(cls):
            return type(NSString)
    
        @classmethod
        def allowsReverseTransformation(cls):
            return False
    
        def transformedValue_(self, value):
            priority = value.unsignedIntValue()
            if priority < CalPriorityHigh:
                return nonePriority
    
            elif priority < CalPriorityMedium:
                return highPriority
    
            elif priority == CalPriorityMedium:
                return normPriority
    
            return lowPriority
    
    
    class CalController(NSObject):
        def awakeFromNib(self):
            # Register a transformer object for easy generation of
            # human-readable priority strings
            #
            # See CalPriorityToStringTransformer implementation below
    
            prioTransformer = CalPriorityToStringTransformer.alloc().init()
            NSValueTransformer.setValueTransformer_forName_(
                prioTransformer, "CalPriorityToStringTransformer"
            )
    
            # Register for notifications on calendars, events and tasks so we can
            # update the GUI to reflect any changes beneath us
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "calendarsChanged:", CalCalendarsChangedExternallyNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "calendarsChanged:", CalCalendarsChangedNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "eventsChanged:", CalEventsChangedExternallyNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "eventsChanged:", CalEventsChangedNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "tasksChanged:", CalTasksChangedExternallyNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "tasksChanged:", CalTasksChangedNotification, None
            )
    
        # Set up the read-only calendars/events/tasks arrays from Calendar Store
        # as observable keys for Cocoa Bindings
        # This in conjunction with the notifications will allow for immediate UI
        # updates whenever calendar data changes outside of this app
        def calendars(self):
            return CalCalendarStore.defaultCalendarStore().calendars()
    
        def events(self):
            store = CalCalendarStore.defaultCalendarStore()
            # Pull all events starting now from all calendars in the CalendarStore
            allEventsPredicate = (
                CalCalendarStore.eventPredicateWithStartDate_endDate_calendars_(
                    NSDate.date(), NSDate.distantFuture(), store.calendars()
                )
            )
            return store.eventsWithPredicate_(allEventsPredicate)
    
        def tasks(self):
            store = CalCalendarStore.defaultCalendarStore()
            # Pull all uncompleted tasks from all calendars in the CalendarStore
            return store.tasksWithPredicate_(
                CalCalendarStore.taskPredicateWithUncompletedTasks_(store.calendars())
            )
    
        # With the observable keys set up above and the appropriate bindings in IB,
        # we can trigger UI updates just by signaling changes to the keys
        def calendarsChanged_(self, notification):
            self.willChangeValueForKey_("calendars")
            self.didChangeValueForKey_("calendars")
    
        def eventsChanged_(self, notification):
            self.willChangeValueForKey_("events")
            self.didChangeValueForKey_("events")
    
        def tasksChanged_(self, notification):
            self.willChangeValueForKey_("tasks")
            self.didChangeValueForKey_("tasks")

.. rst-class:: tabbertab

main.py
.......

.. sourcecode:: python

    import AppController  # noqa: F401
    import CalController  # noqa: F401
    import objc
    from PyObjCTools import AppHelper
    
    objc.setVerbose(True)
    
    
    AppHelper.runEventLoop()

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    Script for building the example.
    
    Usage:
        python3 setup.py py2app
    """
    
    from setuptools import setup
    
    setup(
        name="PyCalendarStore",
        app=["main.py"],
        data_files=["English.lproj"],
        setup_requires=[
            "py2app",
            "pyobjc-framework-CalendarStore",
            "pyobjc-framework-Cocoa",
        ],
    )

